/**
 * 
 */
package spitfire.ksim.algorithm;

import java.util.Collections;
import java.util.List;

import TJavaLib.TList;

/**
 * @author Adam
 *
 */
public class RuleExtractionImpl implements RuleExtractionAlgorithm {

	@Override
	public FuzzyRule extractRule(List<Long> timeStampList,
			List<Double> dataList) {
		
//		List<Long> timeStampL = new ArrayList<Long>(timeStampList); 
		// Check data adequacy
//		if (!PreprocAndValidationAlgorithm.procAndValid(timeStampL, dataList)) {
//			System.out.println("Not enough data for Rule Extraction");
//			return null;
//		}

		Double[] raw = dataList.toArray(new Double[dataList.size()]);
		// Identify the value range
		double rawMax = Collections.max(dataList);
		double rawMin = Collections.min(dataList);
		double rawRange = rawMax - rawMin;
		
		// Special case: all values are the same
		if (rawRange <= Double.MIN_VALUE) {
			System.out.println("All values in snapshot are identical");
			double epsilon = 0.05;
			FuzzyRule rule = new FuzzyRule();
			rule.setrMax(rawMax + epsilon);
			rule.setrMin(rawMin - epsilon);
			rule.add(raw[0] - epsilon, 1);
			rule.add(raw[0] + epsilon, 1);
			return rule;
		}
		
		// Max-Min normalize raw data
		double[] normRaw = new double[raw.length];
		for (int i=0; i<normRaw.length; i++) {
			normRaw[i] = (raw[i] - rawMin) / rawRange;
		}
		
		//Calculate neighborhood density curve of data
		double ra = 0.5;
		double alpha = 4/ra/ra;
		double ndc[] = new double[normRaw.length];
		for (int i=0; i<normRaw.length; i++) {
			ndc[i] = 0;
			for (int j=0; j<normRaw.length; j++) {
				double d = Math.abs(normRaw[i]-normRaw[j]);
				ndc[i] += Math.exp(-alpha*d*d);
			}
		}
		
		// Max-Min normalize neighborhood density
		double ndcMax = Double.MIN_VALUE;
		double ndcMin = Double.MAX_VALUE;
		for (int i=0; i<ndc.length; i++) {
			if (ndcMax < ndc[i]) ndcMax = ndc[i];
			if (ndcMin > ndc[i]) ndcMin = ndc[i];
		}
		double ndcRange = ndcMax - ndcMin;
		for (int i=0; i<ndc.length; i++) {
			ndc[i] = (ndc[i] - ndcMin) / ndcRange;
		}

		//Discretize the neighborhood density curve
		int dSize = 100;
		double delta = rawRange / dSize;

		double dndcX[] = new double[dSize];
		double dndcXN[] = new double[dSize];
		double dndcY[] = new double[dSize];
		int dndcC[] = new int[dSize];
		for (int i=0; i<dSize; i++) {
			dndcX[i] = rawMin + delta/2 + i*delta;
			dndcXN[i] = (1/(double)dSize)/2 + (double)i/(double)dSize;
			dndcY[i] = 0;
			dndcC[i] = 0;
		}
		for (int i=0; i<ndc.length; i++) {
			int dcount = (int)((raw[i] - rawMin) / delta);
			if (dcount >= dSize) dcount = dSize-1;
			dndcY[dcount] += ndc[i];
			dndcC[dcount]++;
		}
		for (int i=0; i<dndcX.length; i++) {
			if (dndcC[i] > 0) {
				dndcY[i] /= dndcC[i];
			}
		}

		/*------ Linearize the discrete neighborhood density curve ------ */
		TList vx = new TList();
		//			TList vxN = new TList();
		TList vy = new TList();
		TList dy = new TList();

		//Eliminate no-data points
		int i1 = 0;
		while (dndcC[i1] <= 0) i1++;
		vx.enList(Double.valueOf(dndcXN[i1]));
		vy.enList(Double.valueOf(dndcY[i1]));
		dy.enList(Double.valueOf(0));
		for (int i2=i1+1; i2<dndcXN.length; i2++) if (dndcC[i2] > 0) {
			vx.enList(Double.valueOf(dndcXN[i2]));
			vy.enList(Double.valueOf(dndcY[i2]));
			double d = (dndcY[i2] - dndcY[i1]) / (dndcXN[i2] - dndcXN[i1]);
			dy.enList(Double.valueOf(d));
			i1 = i2;
		}

		//Linearize the has-data points
		//bugfix: dy can be of size 1, leading to OutOfBoundException
//		if (vx.len() < 2 || vy.len() < 2 || dy.len() < 2) { // temporary solution
//			return null;
//		}
		FuzzyRule rule = new FuzzyRule();
		rule.setrMax(rawMax);
		rule.setrMin(rawMin);
		rule.add((Double)vx.get(0), (Double)vy.get(0));
		if (dy.len() > 1) {
			double thSlope = 1; // PI/4
			double slope1 = ((Double)dy.get(1)).doubleValue();
			for (int i=2; i<dy.len(); i++) {
				double slope2 = ((Double)dy.get(i)).doubleValue();
				double dSlope = Math.abs(slope2-slope1);
				if (dSlope >= thSlope) {
					rule.add((Double)vx.get(i-1), (Double)vy.get(i-1));
					slope1 = slope2;
				}
			}
			rule.add((Double)vx.get(vx.len()-1), (Double)vy.get(vy.len()-1));
		}
		return rule;
	}

}
